package com.hup.minicontroller.common.bluetooth.framework;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.hup.minicontroller.common.bluetooth.handler.BtDataHandler.DataMethodInvoker;
import com.hup.minicontroller.common.bluetooth.handler.BtDataSplitCodecHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtHeartbeatHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtHeartbeatHandler.BtIdleCallback;
import com.hup.minicontroller.common.bluetooth.handler.BtLoginHandler;
import com.hup.minicontroller.common.bluetooth.handler.BtLoginResponseHandler;
import com.hup.minicontroller.common.bluetooth.model.BaseBtDataPacket;
import com.hup.minicontroller.common.bluetooth.model.BtLoginRequestPacket;
import com.hup.minicontroller.common.bluetooth.model.BtLoginResponsePacket;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * BluetoothSocket处理类;
 * 分为两类[服务端收到的socket,客户端连上服务端后的socket]
 * .登陆认证功能和心态功能有区别,其他功能没区别
 * 功能
 * .初始化socket,管理生命周期[上下线,读到数据,出现异常]后都会通知BtChannelHandler
 * .处理消息读取功能:处理[粘包,解码,反序列化],得到数据后通知BtChannelHandler
 * .登陆认证功能,认证通过后,才会有'连接成功'的通知;否则,即使socket连接成功,但未认证通过时都不会有[上下线通知]
 * .心跳功能
 * .消息发送功能
 *
 * @author hugan
 * @date 2021/5/14
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BtChannel implements Runnable {
    private static final String TAG = BtChannel.class.getSimpleName();

    /**
     * true-服务端收到的socket,false-客户端连上服务端后的socket
     */
    private boolean isServer;
    private BluetoothSocket socket;
    private BtChannelHandler channelHandler;
    private Map<Class<? extends BaseBtDataPacket>, DataMethodInvoker> dataHandlerMap;
    private BtLoginHandler loginHandler;
    private BtLoginResponseHandler loginResponseHandler;
    private BtIdleCallback idleCallback;

    //初始化后的属性
    private DataInputStream inputStream;
    private DataOutputStream outputStream;
    private ArrayList<BtBaseFunctionHandler> lstDataHandler;
    @Getter
    private boolean loginValidSuccess;
    @Getter
    private BtLoginRequestPacket loginRequest;
    @Getter
    private BtLoginResponsePacket loginResponse;

    /**
     * 服务端收到的socket
     *
     * @param loginHandler 登陆认证处理器,null-没登陆认证功能
     * @param idleCallback 心跳功能回调,null-无心跳功能;心跳处理器必须是每个client单独实例,所以是传入 {@link BtIdleCallback}而不是 {@link BtHeartbeatHandler}
     */
    public static BtChannel serverChannel(BluetoothSocket socket, BtChannelHandler channelHandler,
                                          Map<Class<? extends BaseBtDataPacket>, DataMethodInvoker> dataHandlerMap,
                                          BtLoginHandler loginHandler, BtIdleCallback idleCallback) {
        BtChannel res = new BtChannel();
        res.isServer = true;
        res.socket = socket;
        res.channelHandler = channelHandler;
        res.dataHandlerMap = dataHandlerMap;
        res.loginHandler = loginHandler;
        res.idleCallback = idleCallback;
        return res;
    }

    /**
     * 客户端连上的socket
     *
     * @param loginResponseHandler 登陆认证处理器,null-没登陆认证功能
     * @param idleCallback 心跳功能回调,null-无心跳功能;心跳处理器必须是每个client单独实例,所以是传入 {@link BtIdleCallback}而不是 {@link BtHeartbeatHandler}
     */
    public static BtChannel clientChannel(BluetoothSocket socket, BtChannelHandler channelHandler,
                                          Map<Class<? extends BaseBtDataPacket>, DataMethodInvoker> dataHandlerMap,
                                          BtLoginResponseHandler loginResponseHandler, BtIdleCallback idleCallback) throws IOException {
        BtChannel res = new BtChannel();
        res.isServer = false;
        res.socket = socket;
        res.channelHandler = channelHandler;
        res.dataHandlerMap = dataHandlerMap;
        res.loginResponseHandler = loginResponseHandler;
        res.idleCallback = idleCallback;
        try {
            res.initStream();
        } catch (IOException e) {
            res.disconnect();
            throw e;
        }
        return res;
    }

    private void initStream() throws IOException {
        inputStream = new DataInputStream(socket.getInputStream());
        outputStream = new DataOutputStream(socket.getOutputStream());
    }

    @Override
    public void run() {
        if (isServer) {
            try {
                initStream();
            } catch (IOException e) {
                channelHandler.exceptionCaught(this, e);
                try {
                    if (socket.isConnected()) socket.close();
                } catch (IOException e2) {
                    Log.w(TAG, "初始化连接异常并关闭异常:" + e2);
                }
                //不会通知server,因为还没登陆认证
                return;
            }
        }
        Log.i(TAG, "onChannelActive: " + socket.getRemoteDevice().getName());
        //初始化处理器
        lstDataHandler = new ArrayList<>();
        if (isServer && loginHandler != null) {
            lstDataHandler.add(loginHandler);
            loginHandler.onChannelActive(this);
        } else if (!isServer && loginResponseHandler != null) {
            lstDataHandler.add(loginResponseHandler);
            loginResponseHandler.onChannelActive(this);
        }
        //心跳处理器必须是每个client单独实例的
        if (idleCallback != null) {
            lstDataHandler.add(new BtHeartbeatHandler(!isServer, this, idleCallback));
        }
        if (loginHandler == null) {
            Log.w(TAG, "run: 目前无配置登陆认证功能!此情况在开发时没测试,模块可能有bug", new Throwable());
            //无登陆认证时,初始化stream成功后就通知[连接成功]
            channelHandler.onConnectResult(true, this);
        }
        //阻塞线程,循环读取数据,直到连接断开
        doReading(inputStream);
        //连接断开:任意一方主动关闭连接
        if (loginHandler == null) {
            channelHandler.onChannelClosed(this);
        } else {
            /*
            .只有认证成功的连接才会有onChannelClosed通知
            .服务端的连接:认证失败的连接,对服务端是无需感知的,不会回调通知server
            .客户端的连接:认证失败时,已在onLoginValidResult通知onConnectResult
             */
            if (loginValidSuccess) channelHandler.onChannelClosed(this);
        }
    }

    private void doReading(DataInputStream inputStream) {
        while (true) {
            try {
                BaseBtDataPacket data = BtDataSplitCodecHandler.INSTANCE.decode(inputStream);
                Iterator<BtBaseFunctionHandler> it = lstDataHandler.iterator();
                while (it.hasNext()) {
                    data = it.next().onReceived(this, data, it);
                    if (data == null) break;//代表数据已被消费,不需要往下传递
                }
                if (data == null) continue;//前面的reader已消费数据

                DataMethodInvoker invoker = dataHandlerMap.get(data.getClass());
                if (invoker != null) {
                    //已指定处理方法的数据
                    try {
                        //log.info("mgs={}", msg);
                        invoker.invoke(data, this);
                    } catch (Exception e) {
                        Log.e(TAG, "channelRead0: 消息处理异常=" + data);
                    }
                } else {
                    //未知数据/未指定处理方法的数据
                    channelHandler.onUnknownData(this, data);
                }
            } catch (Exception e) {
                channelHandler.exceptionCaught(this, e);
                if (!socket.isConnected()) break;
            }
        }
    }

    public void disconnect() {
        try {
            if (socket.isConnected()) socket.close();
        } catch (IOException e2) {
            Log.w(TAG, "disconnect: socket关闭异常:" + e2);
        }
    }

    public boolean isClosed() {
        return !socket.isConnected();
    }

    /**
     * 向客户端发送数据
     *
     * @apiNote 需自行使用子线程调用方法
     */
    public void send(BaseBtDataPacket packet) {
        try {
            BtDataSplitCodecHandler.INSTANCE.encodeAndSend(outputStream, packet);
        } catch (IOException e) {
            channelHandler.exceptionCaught(this, e);
        }
    }

    public BluetoothDevice getRemoteDevice() {
        return socket.getRemoteDevice();
    }

    /**
     * <pre>
     * 服务端回调:仅成功后调用,且loginResponse为null
     * 客户端回调:成功失败都调用,且loginRequest为null
     *
     * @param success 登陆认证是否成功
     */
    public void onLoginValidResult(boolean success, BtLoginRequestPacket loginRequest, BtLoginResponsePacket loginResponse) {
        loginValidSuccess = success;
        this.loginRequest = loginRequest;
        this.loginResponse = loginResponse;
        if (isServer) {
            if (success) channelHandler.onConnectResult(true, this);
        } else {
            channelHandler.onConnectResult(success, this);
        }
    }

}
